﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows.Media.Media3D;

using Microsoft.Win32;

using ObjectEditor;
using ObjectEditor.Editor;
using ObjectEditor.Display3D;

using WLD = EQEmu.Files.WLD;
using S = EQEmu.Files.S3D;
using EQEmu.Files.WLD.Fragments;

namespace EQEmu.S3DPlugin
{
    internal class WldVisual : ModelVisual3D
    {
    }

    public class S3DRibbonTabViewModel : ViewModel, IObjectEditor, IWorldModel3DProvider, IDisposable
    {
        private DelegateCommand _openFileCommand;
        private DelegateCommand _saveSelectedFileCommand;
        private DelegateCommand _renderAllCommand;

        public event EventHandler<ModelChangedEventArgs> ModelChanged;

        #pragma warning disable 67
        public event EventHandler<ObjectSelectedEventArgs> ObjectSelected;
        public event EventHandler<CameraChangeEventArgs> RequestCameraChange;
        #pragma warning restore 67

        private ModelVisual3D _modelVisual = null;
        private S.S3D _archive;
        private WLD.WLD _wld;
        private WLD.WLD _objectLocations = null;
        private WLD.WLD _objects = null;
        private EQEmu.S3D.Display3D.WldDisplay3D _display3d;
        private S.ArchiveFile _selectedFile;
        private Mesh _selectedMesh = null;
        private IEnumerable<WLD.WLD> _wlds;
        private ModelReference _selectedModel = null;
        private int _modelTextureNumber = 0;
        private int _modelHeadNumber = 0;

        public System.Windows.Media.Media3D.ModelVisual3D Model3D
        {
            get { return _modelVisual; }
            private set
            {
                _modelVisual = value;
                OnModelChanged();
                OnPropertyChanged("Model3D");
            }
        }

        protected void OnModelChanged()
        {
            var e = ModelChanged;
            if (e != null)
            {
                e(this, new ModelChangedEventArgs());
            }
        }

        public void UserClicked(System.Windows.Media.Media3D.Point3D point, ObjectEditor.AppInformation info)
        {
            //throw new NotImplementedException();
        }

        public DelegateCommand OpenFileCommand
        {
            get
            {
                if (_openFileCommand == null)
                {
                    _openFileCommand = new DelegateCommand(x =>
                    {
                        var od = new OpenFileDialog();
                        od.Filter = "S3D Files (.s3d)|*.s3d|All Files (*.*)|*.*";
                        if ((bool)od.ShowDialog())
                        {
                            bool loadObj = false;
                            var fname = Path.GetFileName(od.FileName);

                            if (!fname.Contains('_'))
                            {
                                var result = System.Windows.MessageBox.Show("Load objects?", "Load objects?", System.Windows.MessageBoxButton.YesNo);
                                loadObj = result == System.Windows.MessageBoxResult.Yes ? true : false;
                            }

                            bool useNewTextures = true;
                            var result2 = System.Windows.MessageBox.Show("Does this archive use hi-res files? If textures are flipped choose the opposite selection", "Texture Format", System.Windows.MessageBoxButton.YesNo);
                            useNewTextures = result2 == System.Windows.MessageBoxResult.Yes ? true : false;

                            OpenFile(new OpenFileParams()
                            {
                                File = od.FileName,
                                LoadObjects = loadObj,
                                UseNewTextuers = useNewTextures
                            });
                        }
                    }, y =>
                    {
                        return true;
                    });
                }

                return _openFileCommand;
            }
        }
        public DelegateCommand SaveSelectedFileCommand
        {
            get
            {
                if (_saveSelectedFileCommand == null)
                {
                    _saveSelectedFileCommand = new DelegateCommand(
                        x =>
                        {
                            var sd = new SaveFileDialog();
                            sd.FileName = _selectedFile.Name;
                            if ((bool)sd.ShowDialog())
                            {
                                using (var fs = new FileStream(sd.FileName, FileMode.Create))
                                {
                                    fs.Write(_selectedFile.Bytes, 0, _selectedFile.Bytes.Count());
                                }
                            }
                        },
                        y =>
                        {
                            return _selectedFile != null;
                        });
                }
                return _saveSelectedFileCommand;
            }
        }
        public DelegateCommand RenderAllCommand
        {
            get
            {
                if (_renderAllCommand == null)
                {
                    _renderAllCommand = new DelegateCommand(
                        x =>
                        {
                            _display3d.UpdateAll();
                        },
                        y =>
                        {
                            return Meshes != null && _display3d != null;
                        });
                }
                return _renderAllCommand;
            }
        }

        private void OpenFile(OpenFileParams p)
        {
            S.S3D s3d = null;

            string fileName = Path.GetFileNameWithoutExtension(p.File);
            string directory = Path.GetDirectoryName(p.File);
            string zone = fileName.Split('_').ElementAt(0);

            try
            {
                S3DArchive = s3d = S.S3D.Load(p.File);
            }
            catch (Exception e)
            {
                System.Windows.MessageBox.Show("could not load file:" + p.File + " , " + e.Message + Environment.NewLine + e.StackTrace);
                return;
            }

            IEnumerable<S.ArchiveFile> files = null;
            if (p.LoadObjects)
            {
                files = s3d.Files.Where(x => x.Name.Equals("objects.wld") || x.Name.Contains(zone));
            }
            else
            {
                files = s3d.Files.Where(x => x.Name.Contains(zone) && !x.Name.Contains("objects.wld"));
            }

            List<WLD.WLD> wldCollection = new List<WLD.WLD>();
            WLD.WLD displayWld = null;
            WLD.WLD objLocsWld = null;
            WLD.WLD objectsWld = null;

            foreach (var f in files)
            {
                using (var ms = new MemoryStream(f.Bytes))
                {
                    WLD.WLD wld = null;
                    try
                    {
                        wld = WLD.WLD.Load(ms, f.Name);
                    }
                    catch (Exception e)
                    {
                        System.Windows.MessageBox.Show("Failed to read:" + f.Name + "," + e.Message + Environment.NewLine + e.StackTrace);
                        continue;
                    }
                    wldCollection.Add(wld);
                    if (f.Name.Equals(fileName + ".wld"))
                    {
                        displayWld = wld;
                        if (f.Name.Contains("_obj") || f.Name.Contains("_chr"))
                        {
                            wld.ResolveMeshNames();
                        }
                    }
                    else if (f.Name.Equals("objects.wld"))
                    {
                        objLocsWld = wld;
                        wld.ResolveObjectLocationNames();
                    }
                }
            }

            if (p.LoadObjects)
            {
                var objFile = directory + "/" + fileName + "_obj.s3d";
                if (File.Exists(objFile))
                {
                    var objS3d = S.S3D.Load(objFile);
                    var archive = objS3d.Files.FirstOrDefault(x => x.Name.Contains(".wld"));
                    if (archive != null)
                    {
                        using (var ms = new MemoryStream(archive.Bytes))
                        {
                            WLD.WLD wld = null;
                            try
                            {
                                wld = WLD.WLD.Load(ms, archive.Name);
                            }
                            catch (Exception e)
                            {
                                System.Windows.MessageBox.Show("Failed to load objects:" + archive.Name + "," + e.Message + Environment.NewLine + e.StackTrace);
                            }
                            wldCollection.Add(wld);
                            objectsWld = wld;
                            objectsWld.Files = objS3d;
                        }
                        if (objectsWld != null)
                        {
                            objectsWld.ResolveMeshNames();
                        }
                    }
                }
            }

            _objectLocations = objLocsWld;
            _wld = displayWld;
            _objects = objectsWld;

            if (_wld != null)
            {
                _wld.Files = s3d;
                WLDObject = _wld;
                _wlds = wldCollection;
            }
        }

        private WLD.WLD WLDObject
        {
            get { return _wld; }
            set
            {
                _wld = value;

                if (_display3d != null)
                {
                    _display3d.Dispose();
                }

                //_display3d = new EQEmuDisplay3D.WldDisplay3D(_wld, _objectLocations, _objects);
                _display3d = new S3D.Display3D.WldDisplay3D(_wld, _objectLocations, _objects);
                _display3d.UpdateAll();

                Model3D = new WldVisual()
                {
                    Content = _display3d.Model,                
                };

                RenderAllCommand.RaiseCanExecuteChanged();
                OnPropertyChanged("Meshes");
                OnPropertyChanged("Models");
                OnPropertyChanged("WLDObject");
                OnPropertyChanged("WLDCollection");
            }
        }

        public S.S3D S3DArchive
        {
            get { return _archive; }
            private set
            {
                _archive = value;
                SaveSelectedFileCommand.RaiseCanExecuteChanged();
                OnPropertyChanged("Files");
                OnPropertyChanged("S3DArchive");
            }
        }

        public IEnumerable<WLD.WLD> WLDCollection
        {
            get { return _wlds; }
        }

        public S.ArchiveFile SelectedFile
        {
            get { return _selectedFile; }
            set
            {
                _selectedFile = value;
                OnPropertyChanged("SelectedFile");
                SaveSelectedFileCommand.RaiseCanExecuteChanged();
            }
        }
        
        public WLD.Fragments.Mesh SelectedMesh
        {
            get { return _selectedMesh; }
            set
            {
                _selectedMesh = value;
                if (value != null && _display3d != null)
                {
                    _display3d.RenderMesh(new WLD.Fragments.Mesh[] { value });
                }
                OnPropertyChanged("SelectedMesh");
            }
        }

        public ModelReference SelectedModel
        {
            get { return _selectedModel; }
            set
            {
                _selectedModel = value;
                RenderModel();
                OnPropertyChanged("SelectedModel");
            }
        }

        public IEnumerable<WLD.Fragments.Mesh> Meshes
        {
            get
            {
                if (WLDObject != null)
                {
                    return WLDObject.ZoneMeshes;
                }
                else return null;
            }
        }

        public IEnumerable<ModelReference> Models
        {
            get
            {
                if (WLDObject != null)
                {
                    return WLDObject.Models;
                }
                else return null;
            }
        }

        private void RenderModel()
        {
            if (_display3d != null && SelectedModel != null)
            {
                _display3d.RenderModel(SelectedModel, _modelTextureNumber, _modelHeadNumber);
            }
        }

        public int TextureNumber
        {
            get { return _modelTextureNumber; }
            set
            {
                    _modelTextureNumber = value;
                RenderModel();                
                OnPropertyChanged("TextureNumber");
            }
        }

        public int HeadNumber
        {
            get { return _modelHeadNumber; }
            set
            {
                _modelHeadNumber = value;
                RenderModel();
                OnPropertyChanged("HeadNumber");
            }
        }

        public IEnumerable<S.ArchiveFile> Files
        {
            get
            {
                if (_archive != null)
                {
                    return _archive.Files;
                }
                else return null;
            }
        }

        private class OpenFileParams
        {
            public bool LoadObjects { get; set; }
            public bool UseNewTextuers { get; set; }
            public string File { get; set; }
        }
        
        public void ConstrainView(ViewConstraints constraints)
        {
            if (_display3d == null) return;

            _display3d.UpdateView(new EQEmu.Display3D.ViewConstraints()
            {
                MaxX = constraints.MaxX,
                MaxY = constraints.MaxY,
                MaxZ = constraints.MaxZ,
                MinX = constraints.MinX,
                MinY = constraints.MinY,
                MinZ = constraints.MinZ
            });
        }

        protected virtual void Dispose(bool disposeAll)
        {
            if (_display3d != null)
            {
                _display3d.Dispose();
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
    }
}
